// ------------------------------------------------------------------------------------
// REVISION LOG ENTRY
// Revision By: (C) Copyright 2014 Hiroyuki Tominaga, All Rights Reserved.
//
// File name  : tkit-loader.js
// Revised on : 2014-07-01 04:12:29
// Summary    : Tkit primary script loader.
// CDN:
//    https://js-tkit-project.googlecode.com/svn/trunk/tkit-loader.js
//    http://jpropedit-gui.sourceforge.jp/js-tkit/tkit-loader.js
//    https://s3-ap-northeast-1.amazonaws.com/motrohi/js/tkit/tkit-loader.js
//
//    *https://cdn.rawgit.com/motrohi/js-projects/master/js-tkit/tkit-loader.js (ancillary
//
// Usage:
//    https://js-tkit-project.googlecode.com/svn/trunk/tkit-loader.js?depends=jquery,jsafx
//    https://js-tkit-project.googlecode.com/svn/trunk/tkit-loader.js?depends=1 (or true
//
//    https://js-tkit-project.googlecode.com/svn/trunk/tkit-loader.js?depends=1&devmode=1 (or true
//    https://cdn.rawgit.com/motrohi/js-projects/master/js-tkit/tkit-loader.js?depends=1&devmode=1 (or true
//
//
// Required   : jsafx
/*

https://cdn.rawgit.com/motrohi/js-projects/master/js-tkit/
https://js-tkit-project.googlecode.com/svn/trunk/


Specifications:

  1. location query parameter に defer=1 を渡すと, 主要な依存 script に defer attribute を付加します.
         -> tkit.html?defer=1

  2. tkit3.js#bindClasses では, まず class module が load されているかどうか検証し,
     not loaded の場合, load manager に module script の load request を出します.

     この時, bindClasses の parameter#3 に "tkit-loaded-class" のような string を渡した場合,
     window["tkitLoadedClass"] として instance が追加される object が bind されます.(html element id)
     parameter#3 の文字列に "-" が混じっている場合, "-" は削除され直後の文字が大文字に変換された文字列になります.

     また, この bind は class の instance 構築, load request の process に入る前に行われるため,
     timer を使って進歩状況を観察することが可能です.

  3. すべての module が load されるといっせいに tkit class の instance 構築が始まります.

  4. すべて構築が完了後, bindClasses function に渡した callback function, および事前に
     load manager の "addLoadListener" で登録した function すべてに instance が bind された plain object
     が渡されます.

     { <html element id>: <tkit class instance>, <html element id>: <tkit class instance>, <html element id>: <tkit class instance> };

     また, bindClasses function からも, instance がすべて bind された plain object が返却されます.

Specifications:

  1. If you pass a "defer=1" to location query parameter, you can add the defer attribute to the major dependent script.
        -> tkit.html?defer=1

  2. In tkit3.js # bindClasses, validates class module whether it is first load,
     If not loaded, I will issue a load request of module script to load manager.

  3. Instance construction of tkit class will begin simultaneously module of all is load.

  4. Construction is complete all, callback function that was passed to bindClasses function,
     and plain object to which the instance is to bind all function that has been registered with the "addLoadListener" of pre-load manager is passed.

     Also, from bindClasses function, plain object to which the instance is bind all is returned.

*/
// ------------------------------------------------------------------------------------

/**
 * class Tkit.*
 */
(function(name_space, loader_name)
{    /* Strict mode for this plugin */
    "use strict";

    // ----------------------------------- define closure section -----------------------------------
    var _DEBUG = false;
    var tkit; {
        var W = (window || self);
        /* */
        tkit = W[name_space];
        W.$ = W.$ || {};
        if (!tkit)
            tkit = W[name_space] = {};
        //
        if (!W.$.noop) {
            W.$.noop = function(){};
        }
    }

    var util = {
        // ---------------- constants.
        CLASS_NAME: 0,
        ELEMENT: 1,
        CONFIG: 2,

        /* 内部 flag. */
        PRIM_FLAG_TKIT: 0x30,

        script_load: function () {
            // note: 2014-07-01 15:18:53 ここで console.log が dead lock を起こす case があった (chrome
            //console.log("script_load, args=%o", arguments);
            if (
                !this.readyState || /loaded|complete/.test(this.readyState)
            ) {
                // Handle memory leak in IE
                this.onerror = this.onload = this.onreadystatechange = null;
                closure_context_queue.length && loader.checkPending();
                // Remove the script
                if (!this.no_remove && this.parentNode) {
                    this.parentNode.removeChild(this);
                }
            }
        },
        array_remove: function (any) {
            for (var i = 0; i < this.length; i++) {
                if (this[i] === any) {
                    this.splice(i, 1); break;
                }
            }
        },
        // from jsafx.parseCurrentQuery
        pcq: function () {
            var q = location.search;
            console.log(q);
            if (q) {
                var map = {}, pairs = q.substring(1).split("&");
                for (var i in pairs) {
                    var pair = pairs[i].split("=");
                    map[pair[0]] = pair[1];
                }
                return map;
            }
            return null;
        },
        is_validkey: function(key) {
            return /jquery|jsafx/i.test(key);
        },
        lookup: function(key) {
            var list = map;
            for(var i = list.length; --i >= 0;) {
                var obj = list[i];
                if (key in obj)
                    return obj[key];
            }
        }
    };

    var loader;
    // "use strict" では暗黙的宣言は error となる.
    var
    closure_created_objects = {}, /* created instance place holder. */
    closure_context_queue = [],   /* pending class instance context queue(array */
    closure_load_listeners = [],  /* load event listener functions. */
    closure_pending_count = 0,    /* pending count */

    head = document.querySelector('head'),//document.querySelectorAll('head')[0],
    //var script_base: "https://js-tkit-project.googlecode.com/svn/trunk/",
    //var script_base: "https://cdn.rawgit.com/motrohi/js-projects/master/js-tkit/",
    script_base = "http://jpropedit-gui.sourceforge.jp/js-tkit/";

    var map = [
        { jquery: "https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min" },
        { jsafx:  "https://js-extension-afx.googlecode.com/svn/trunk/jsafx" },
        { tkit3:   script_base + "tkit3" }
    ];
    // ----------------------------------- define closure section -----------------------------------

    /**
     * get loader singleton instance.
     */
    tkit.getLoader = function() {
        return tkit[loader_name];
    };

    /** class module 定義時に利用.
     * 自動的に constructor property が設定されるが, 記述がやや冗長になる.
     */
    tkit.buildClass = function(className, ctor, proto) {
        var clazz = tkit[className] = ctor;
        clazz.qname = name_space + "." + className;
        clazz.prototype = proto;
        clazz.prototype.constructor = clazz;
    };
    var initialized = false;
    var PROCESSED = 0x20, UNDEFINED = 0x10;
    /**
     * class Tkit.ScriptLoader (singleton
     */
    loader = tkit[loader_name] =
    {    // example.html?depends=1&devmode=1 も可能としていたが廃止.
        // script attr "data-depends", "data-devmode" のみとした.
        query: util.pcq(),
        // note: 2014-07-01 07:31:16 この function は export すべきではないかもしれない.
        // execute only once.
        init: function() {
            if (initialized)
                return;
            var depends = '', devmode = ""; {
                var sc = document.querySelector("[data-depends]");
                if (sc) depends = sc.getAttribute("data-depends");
                sc = document.querySelector("[data-devmode]");
                if (sc) devmode = sc.getAttribute("data-devmode");
            }
            var in_develop = false, depends_all = false;
            if (depends && depends.length) {
                depends = depends.split(",");
                if ( /1|ture/i.test(depends[0]) ) { // value[0] is "1" or "true"?
                    depends = true;
                } else {
                    depends.forEach(function(e, i, arry) {  // value is key(jquery or jsafx)
                        var key = e.toLowerCase();
                        if (util.is_validkey(key))
                            loader.require(null, in_develop/*false*/, util.lookup(key));
                    });
                    depends = false;
                }
            }
            if (devmode && devmode.length) {
                in_develop = /1|ture/.test( devmode.toLowerCase() );
            }

            if (depends) {
                map.forEach(function(e, i, arry) {
                    for (var p in e) {
                        if ( p !== "tkit3" && util.is_validkey(p) )
                            loader.require(null, in_develop, e[p]);
                    }
                })
            }
            // tkit3 は tkit core script なので別腹
            this.require(null, in_develop, util.lookup("tkit3"));
            initialized = true;
        },

        /**
         * change load script base url.
         */
        setLoaderBase: function(url) {
            if (!/\/$/.test(url))
                url += "/";
            script_base = url;
        },
        addLoadListener: function (l) {
            if (typeof l === "function") closure_load_listeners.push(l);
        },
        removeLoadListener: function (l) {
            if (typeof l === "function") {
                util.array_remove.call(closure_load_listeners, l);
            }
        },

        // pdata には文字列のみ渡すことが出来る,
        // その場合拡張子を除いた absolute URL であること.
        // それ以外の場合は, tkit script として URL string が構築される.
        toScriptURL: function (className, in_develop, pdata) {
            var postfix = in_develop? ".js?id=" + new Date().getTime(): ".js";
            if (typeof pdata === "string")
                return pdata + postfix;
               return script_base + "tkit-" + className.toLowerCase() + postfix;
        },

        exports: function(name_space) {
            if (typeof name_space === "string")
                window[name_space] = closure_created_objects;
            //else
            return closure_created_objects;
        },
        addInstance: function(instance) {
            closure_created_objects[instance.id()] = instance;
        },
        // for debug.
        getQueue: function() {
            return closure_context_queue;
        },

        /**
         * @param array      pass initialization context of class as an array.
         *                   [<className>, <html element>, <config id>]
         * @param in_develop usage for in development mode.
         * @param pdata    normally, this parameter is unnecessary. (internal use
         */
        schedule: function(array, in_develop, pdata)
        {
            if (array !== null && "length" in array && tkit[ array[0] ] === undefined) {
                closure_pending_count++;
                closure_context_queue.push(array);
            }

            var script = document.createElement('script');
            script.onload = script.onerror = script.onreadystatechange = function() {
                if (pdata !== undefined)
                    this.no_remove = true;
                util.script_load.apply(this, arguments);
            };

            script.type = 'text/javascript';
            if (this.query && this.query.defer/*pdata !== undefined*/)
                script.defer = true;

            head.insertBefore(script, head.lastChild);
            script.src = this.toScriptURL(
                (!pdata? array[util.CLASS_NAME]: null), in_develop, pdata
            );
        },
        checkPending: function checkPending() {
            if ( --closure_pending_count <= 0 && closure_context_queue.length > 0 ) {
                tkit.DEBUG && console.log("load done, closure_pending_count=%d", closure_pending_count);
                // [fqdn, element, config]
                closure_context_queue.forEach(function(e, i, arry) {
                    var tkit_class = new tkit[ e[util.CLASS_NAME] ]( e[util.ELEMENT] );
                    closure_created_objects[tkit_class.id()] = tkit_class;
                    tkit.DEBUG && console.log("_load_helper, class=%o", tkit_class);
                    tkit_class.setOptions(e[util.CONFIG]);
                });
                // Class load of all completed.
                callSequentially(closure_load_listeners, closure_created_objects); /* from jsafx.js */
                //this.release();
                closure_context_queue = [];   // initialize queue.
                closure_pending_count = 0;
            }
        },

        /**
         * loader をもう利用しない場合 instance を cleanup 出来ます.
         */
        release: function () {
            if (closure_pending_count === 0) {
                var flags = 0;
                for (var i in this) {
                    if (this.hasOwnProperty(i)) // delete this[i] << 0; JSHint says "variables should not be deleted".
                        flags = flags << 1 | (delete this[i]) << 0;
                }
                flags = flags << 1 | (delete tkit[loader_name]) << 0;
                console.log("delete own=%s", flags.toString(2));
                flags = 0;
                for (var i in util) {
                    if (util.hasOwnProperty(i)) // delete this[i] << 0; JSHint says "variables should not be deleted".
                        flags = flags << 1 | (delete util[i]) << 0;
                }
                console.log("delete util=%s", flags.toString(2));
            } else {
                console.log("load is not completed, cannot release");
            }
            /* NG in "use strict";
            delete closure_created_objects;
            delete closure_context_queue;
            delete closure_load_listeners;
            delete closure_pending_count;*/
        }
    };
    // 今後 require に funciton name を移行
    loader.require = loader.schedule;

    loader.qname = name_space + "." + loader_name;
    loader.init();
    //loader.require(null, true, true);

})("Tkit", "ScriptLoader");    // (function()

